package cn.com.zero.manager.gatewaytest.filter;

import cn.com.zero.api.auth.config.properties.TokenProperties;
import cn.com.zero.api.auth.po.MenuPO;
import cn.com.zero.api.auth.po.PermissionPO;
import cn.com.zero.api.auth.util.TokenUtil;
import cn.com.zero.api.auth.util.UserCacheKeyUtil;
import cn.com.zero.base.exception.ErrorCodeException;
import cn.com.zero.base.util.RedisUtil;
import cn.com.zero.common.gateway.enums.LoginClientEnum;
import cn.com.zero.manager.auth.api.constant.TokenKeyConstant;
import cn.com.zero.manager.gatewaytest.vo.OnlineVerificationTimeVO;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author Yang Yao
 * @version 1.0.0
 * @Description 鉴权过滤器
 * @createTime 2023/2/28 16:33
 */
@Component
public class AuthenticationFilter implements GlobalFilter, Ordered {

    /**
     * 输出日志对象
     */
    protected Logger logger = LoggerFactory.getLogger(this.getClass());

    private RedisUtil redisUtil;

    /**
     * token有效时间
     */
    @Value(TokenKeyConstant.EXPIRETIME)
    private String expireTime;

    private TokenUtil tokenUtil;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //获取请求头
        HttpHeaders headers = request.getHeaders();
        String userId;
        String token;
        String loginClient;
        // 前端请求服务的路径
        String path = request.getURI().getPath();
        // 去掉第一个/
        path = path.substring(1);
        // 去掉路径中的服务名，直接找权限路径url
        path = path.substring(path.indexOf("/"));

        // 校验请求路径是否属于公共权限路径
        if (checkPath(path)) return chain.filter(exchange);
        //请求头中获取令牌
        userId = headers.getFirst(TokenKeyConstant.USERID);
        token = headers.getFirst(TokenKeyConstant.TOKEN);
        loginClient = headers.getFirst(TokenKeyConstant.LOGINCLIENT);
        if (StrUtil.isBlank(userId) || StrUtil.isBlank(token) || StrUtil.isBlank(loginClient)) {
            logger.error("请求参数非法,userId[{}],token[{}],loginClient[{}]", userId, token, loginClient);
            throw new ErrorCodeException(-999998);
        }
        // 检验token
        checkToken(token, response, userId, loginClient);

        if (checkIsPermission(path, userId, loginClient)) return chain.filter(exchange);
        throw new ErrorCodeException(-999990L);
    }

    /**
     * 检查该用户是否具有访问此路径的权限信息
     *
     * @param path   请求路径
     * @param userid 用户ID
     * @return true-具有权限 false-无权访问
     */
    private boolean checkIsPermission(String path, String userid, String loginClient) {
        List<String> paths = new ArrayList<>();
        // 获取用户只要登录了就有的权限列表
        List<PermissionPO> selectPublicPermissionIsLogin = redisUtil.getList(UserCacheKeyUtil.getPublicLoginPermissionCacheKey(TokenKeyConstant.MANAGE_MS_KEY));
        for (PermissionPO permissionPO : selectPublicPermissionIsLogin) {
            paths.add(permissionPO.getUrl());
        }
        if (selectPublicPermissionIsLogin.contains(path)) {
            return true;
        }

        // 从redis中获取该用户的权限信息 auth：代表权限对象集合List<MenuPO>的字符串
        List<MenuPO> menuPOS = redisUtil.getList(UserCacheKeyUtil.getMenuPermissionCacheKey(TokenKeyConstant.MANAGE_MS_KEY, loginClient, userid));
        menuPOS.removeIf(t -> {
            if (StrUtil.isBlank(t.getUrl())) {
                return true;
            }
            return false;
        });
        List<String> permission = new ArrayList<>();
        for (MenuPO menuPO : menuPOS) {
            permission.add(menuPO.getUrl());
        }
        for (String url : permission) {
            if (url.lastIndexOf("*") != -1) {
                String regex = path.substring(0, path.lastIndexOf("/") + 1);
                String partUrl = url.substring(0, url.lastIndexOf("/") + 1);
                if (partUrl.equals(regex)) {
                    return true;
                }
            } else {
                if (url.equals(path)) {
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * 校验请求头中的token信息，及获取用户ID
     *
     * @param token       token信息
     * @param response    响应体
     * @param userId      用户代码
     * @param loginClient 登录设备
     * @return 用户ID
     */
    private void checkToken(String token, ServerHttpResponse response, String userId, String loginClient) {
        if (StrUtil.isBlank(token)) {
            throw new ErrorCodeException(-999990L);
        }
        // token的签发时间
        String tokenIssueTimeCacheKey = UserCacheKeyUtil.getUserTokenIssueTimeCacheKey(TokenKeyConstant.MANAGE_MS_KEY, loginClient, userId);
        // 获取当前用户信息
        String tokenSubject = tokenUtil.getTokenSubject(token, String.class);
        if (!userId.equals(tokenSubject)) {
            logger.error("请求参数非法,tokenSubject:{},userId:{}", tokenSubject, userId);
            throw new ErrorCodeException(-999998);
        }
        Date currentTimeMillis = new Date(System.currentTimeMillis());
        String refreshToken = tokenUtil.checkAndRefreshToken(token, currentTimeMillis, (Date) redisUtil.getObject(tokenIssueTimeCacheKey), String.class);
        if (!token.equals(refreshToken)) {
            // 从redis中获取当前用户信息的key
            redisUtil.setObjectAndTime(tokenIssueTimeCacheKey, currentTimeMillis, Long.parseLong(expireTime), TimeUnit.SECONDS);
            redisUtil.expire(UserCacheKeyUtil.getOnlineUserCacheKey(TokenKeyConstant.MANAGE_MS_KEY, loginClient, userId), Long.parseLong(expireTime),
                    TimeUnit.SECONDS);
            redisUtil.expire(UserCacheKeyUtil.getMenuPermissionCacheKey(TokenKeyConstant.MANAGE_MS_KEY, loginClient, userId),
                    Long.parseLong(expireTime), TimeUnit.SECONDS);
            HttpHeaders responseHeaders = response.getHeaders();
            responseHeaders.set("token", refreshToken);
        }
    }

    /**
     * 校验请求路径是否属于公共权限路径
     *
     * @param path
     * @return
     */
    private boolean checkPath(String path) {
        List<String> urls = new ArrayList<>();
        //获取公共权限
        List<PermissionPO> publicPermissionPOS = redisUtil.getList(UserCacheKeyUtil.getPublicPermissionCacheKey(TokenKeyConstant.MANAGE_MS_KEY));
        if (CollectionUtils.isEmpty(publicPermissionPOS)) {
            logger.error("公共权限未加载完毕");
            throw new ErrorCodeException(-10000);
        }
        for (PermissionPO publicPermissionPO : publicPermissionPOS) {
            urls.add(publicPermissionPO.getUrl());
        }
        if (urls.contains(path)) {
            return true;
        }
        return false;
    }


    @Override
    public int getOrder() {
        return 0;
    }

    @Autowired
    public void setRedisUtil(RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
    }

    @Resource
    public void setTokenUtil(TokenUtil tokenUtil) {
        this.tokenUtil = tokenUtil;
    }
}
